﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Diagnostics;

using InfFront.UICore;

namespace InfFront.InfoViews.Base
{
    /// <summary>
    /// View-side IInfoEntry wrapper
    /// </summary>
    internal sealed class InfoViewRecord : ICustomTypeDescriptor, IEditableObject, IDisposable, INotifyPropertyChanged, IIconedObject
    {
        // Fields
        private IInfoEntry origin;
        private InfoViewSource owner;
        private bool isEdited;
        private Dictionary<InfoViewField, Object> pendingUpdates = null;
        private EventHandlerList events = null;

        public InfoViewRecord(IInfoEntry entry, InfoViewSource owner)
        {
            this.origin = entry;
            this.owner = owner;
        }

        public IInfoEntry Origin
        {
            get { return origin; }
        }

        public InfoViewSource Owner
        {
            get { return owner; }
        }

        public bool IsDisposed
        {
            get { return origin == null; }
        }

        public object GetFieldValue(InfoViewField field)
        {
            if (pendingUpdates != null)
            {
                object value = null;
                if (pendingUpdates.TryGetValue(field, out value))
                    return value;
            }

            return Owner.GetFieldValue(field, this);
        }

        public object GetDisplayFieldValue(InfoViewField field)
        {
            var value = GetFieldValue(field);
            var refDisplayField = field.ReferencedDisplayField;
            if (refDisplayField != null)
            {
                var refField = field.ReferencedField;
                if (refField != null)
                {
                    foreach (var rec in refField.Owner.FindEntries(refField, value, 1))
                    {
                        value = rec.GetDisplayFieldValue(refDisplayField);
                        break;
                    }
                }
            }
            return value;
        }

        public void SetFieldValue(InfoViewField field, object value)
        {
            if (!isEdited)
            {
                owner.SetFieldValue(this, field, value);
            }
            else
            {
                if (pendingUpdates == null)
                    pendingUpdates = new Dictionary<InfoViewField, Object>();
                pendingUpdates[field] = value;
            }
            FirePropertyChanged(field.Name); // is it correct to broadcast property changed until the server has said us so?
        }

        public override bool Equals(object obj)
        {
            var asEntry = obj as InfoViewRecord;
            return (asEntry != null) && asEntry.Origin.Equals(Origin) && asEntry.Owner.Equals(Owner);
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public override string ToString()
        {
            var fld = Owner.TitleField;
            if (fld != null)
            {
                var val = GetFieldValue(fld);
                if (val != null)
                    return val.ToString();
                return string.Empty;
            }
            return Owner.Title;
        }

        private EventHandlerList Events
        {
            get
            {
                if (events == null)
                    events = new EventHandlerList();
                return events;
            }
        }

        #region ICustomTypeDescriptor Members

        AttributeCollection ICustomTypeDescriptor.GetAttributes()
        {
            return AttributeCollection.Empty;
        }

        public string GetClassName()
        {
            return Owner.Name + ":Entry";
        }

        string ICustomTypeDescriptor.GetComponentName()
        {
            return GetClassName();
        }

        TypeConverter ICustomTypeDescriptor.GetConverter()
        {
            return null;
        }

        EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
        {
            return null;
        }

        PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
        {
            return null;
        }

        object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
        {
            return null;
        }

        EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
        {
            return EventDescriptorCollection.Empty;
        }

        EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
        {
            return EventDescriptorCollection.Empty;
        }

        PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
        {
            return Owner.GetRecordProperties();
        }

        PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
        {
            return Owner.GetRecordProperties();
        }

        object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
        {
            return this;
        }

        #endregion

        #region IEditableObject Members

        public void BeginEdit()
        {
            if (Owner.NeedsEditBuffer)
                isEdited = true;
        }

        public void CancelEdit()
        {
            if (pendingUpdates != null && pendingUpdates.Count > 0)
            {
                PropertyChangedEventHandler handler = null;
                if (events != null)
                    handler = (PropertyChangedEventHandler)events[EVENT_PROPERTYCHANGED];
                if (handler == null)
                    pendingUpdates.Clear();
                else
                {
                    var savedKeys = new List<InfoViewField>(pendingUpdates.Keys);
                    pendingUpdates.Clear();
                    foreach (var key in savedKeys)
                    {
                        var args = new PropertyChangedEventArgs(key.Name);
                        handler(this, args);
                    }
                }
            }
            isEdited = false;
        }

        public void EndEdit()
        {
            ApplyEdit();
            isEdited = false;
        }

        public void ApplyEdit()
        {
            if (pendingUpdates != null)
            {
                owner.SetFieldValues(this, pendingUpdates);
                pendingUpdates.Clear();
            }
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            if (origin != null)
            {
                origin.Dispose();
                origin = null;
                owner = null;
            }
        }

        #endregion

        #region INotifyPropertyChanged Members

        private static readonly object EVENT_PROPERTYCHANGED = new object();

        public event PropertyChangedEventHandler PropertyChanged
        {
            add { Events.AddHandler(EVENT_PROPERTYCHANGED, value); }
            remove { Events.RemoveHandler(EVENT_PROPERTYCHANGED, value); }
        }

        private void FirePropertyChanged(string name)
        {
            if (events == null)
                return;
            var handler = (PropertyChangedEventHandler)events[EVENT_PROPERTYCHANGED];
            if (handler != null)
            {
                var args = new PropertyChangedEventArgs(name);
                handler(this, args);
            }
        }

        public void FirePropertiesChanged()
        {
            if (pendingUpdates != null)
                pendingUpdates.Clear();
            if (events == null)
                return;
            var handler = (PropertyChangedEventHandler)events[EVENT_PROPERTYCHANGED];
            if (handler != null)
            {
                var args = new PropertyChangedEventArgs(string.Empty);
                handler(this, args);
            }
        }

        #endregion

        #region IIconedObject Members

        System.IO.Stream IIconedObject.GetIconStreamPng(int hSize, int vSize, bool disabled, bool highlighted)
        {
            var fld = Owner.IconField;
            return Owner.GetImageStream(hSize, vSize, disabled, highlighted, 
                                        (fld != null) ? this : null, fld);
        }

        #endregion
    }

    /// <summary>
    /// PropertyDescriptor wrapping the InfoFieldField
    /// </summary>
    internal sealed class InfoFieldPropertyDescriptor : PropertyDescriptor
    {
        // Fields
        private InfoViewField field;
        private bool? isReadOnly;
        private Type type;

        // Methods
        internal InfoFieldPropertyDescriptor(InfoViewField field)
            : base(field.Name, null)
        {
            this.field = field;
            if (field.ReferencedDisplayField != null)
            {
                isReadOnly = true;
                type = field.ReferencedDisplayField.EffectiveType;
            }
        }

        public override bool CanResetValue(object component)
        {
            throw new NotSupportedException("!");
        }

        public override bool Equals(object other)
        {
            if (other is InfoFieldPropertyDescriptor)
            {
                InfoFieldPropertyDescriptor descriptor = (InfoFieldPropertyDescriptor)other;
                return descriptor.field.Equals(field);
            }
            return false;
        }

        public int Order
        {
            get { return field.Order; }
        }

        public override int GetHashCode()
        {
            return field.GetHashCode();
        }

        public override object GetValue(object component)
        {
            var entry = (InfoViewRecord)component;
            if (isReadOnly.HasValue && isReadOnly.Value)
                return entry.GetDisplayFieldValue(field);
            return entry.GetFieldValue(field);
        }

        public override void ResetValue(object component)
        {
            throw new NotSupportedException("!");
        }

        public override void SetValue(object component, object value)
        {
            Debug.Assert(!IsReadOnly);
            var entry = (InfoViewRecord)component;
            entry.SetFieldValue(field, value);
            OnValueChanged(component, EventArgs.Empty);
        }

        public override bool ShouldSerializeValue(object component)
        {
            return false;
        }

        // Properties
        public override Type ComponentType
        {
            get { return null; }
        }

        public override bool IsBrowsable
        {
            get { return true; }
        }

        public override bool IsReadOnly
        {
            get
            {
                if (!isReadOnly.HasValue)
                    isReadOnly = field.IsReadOnly;
                return isReadOnly.Value;
            }
        }

        public override Type PropertyType
        {
            get
            {
                if (type == null)
                    type = field.EffectiveDisplayType;
                return type;
            }
        }
    }
}
